package com.sxt.controller;

import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;

import org.apache.dubbo.config.annotation.Reference;
import org.apache.shiro.SecurityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.http.ResponseEntity;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.sxt.config.FSTRedisSerializer;
import com.sxt.entity.OrderSettlement;
import com.sxt.entity.User;
import com.sxt.entity.UserAddr;
import com.sxt.model.MoneyResult;
import com.sxt.model.PayModel;
import com.sxt.model.ShopCartItem;
import com.sxt.param.OrderParam;
import com.sxt.service.BasketService;
import com.sxt.service.EgoPayService;
import com.sxt.service.OrderService;
import com.sxt.service.OrderSettlementService;
import com.sxt.service.UserAddrService;
import com.sxt.utils.FstSerializationUtils;
import com.sxt.vo.basket.CartResultVo;
import com.sxt.vo.basket.ShopCartItemDiscount;
import com.sxt.vo.order.OrderVo;

import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.map.MapBuilder;
import cn.hutool.core.map.MapUtil;
import io.swagger.annotations.ApiOperation;

/**
 * 订单的相关接口的实现
 * @author WHSXT-LTD
 *
 */
@RestController
public class OrderController {

	@Autowired
	private BasketService basketService ;

	@Autowired
	private UserAddrService userAddrService ;

	@Autowired
	private RedisTemplate<Object, Object> redisTemplate ;
	
	@Autowired
	private StringRedisTemplate redis ;

	@Autowired
	private JmsTemplate jmsTemplate ;


	@Reference(check = false)
	private OrderSettlementService orderSettlementService ;


	@Reference(check = false)
	private EgoPayService egoPayService ;

	private static final String ORDER_PRE = "order:pre:" ;

	private static final String PROD_STOCK = "stock:prod:" ;// stock:prod:1   1000
	private static final String SKU_STOCK = "stock:sku:" ;  // stock:sku:2    1000 

	@Autowired 
	private Snowflake snowFlake ;

	@PostConstruct
	public void init() {
		redisTemplate.setKeySerializer(new StringRedisSerializer());
		// 在json的序列化里面，存在LocalDataTime的序列化问题
		//		redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
		redisTemplate.setValueSerializer(new FSTRedisSerializer());
	}

	/**
	 * 确认下单
	 * @return
	 */
	@ApiOperation("确认下单操作")
	@PostMapping("/p/order/confirm")
	public ResponseEntity<OrderVo> confirm(@RequestBody OrderParam orderParam){
		OrderVo orderVo = new OrderVo() ;


		List<ShopCartItem> shopCartItem  = basketService.getOrderConfirm(orderParam.getBasketIds()) ;
		List<CartResultVo> cartResultVos = builderResult(shopCartItem); // 该集合只要一个值，构建该集合的原因是因为前端的需要

		orderVo.setShopCartOrders(cartResultVos);
		MoneyResult moneyResult = basketService.getTotalMoney(orderParam.getBasketIds());

		CartResultVo cartResultVo = cartResultVos.get(0);

		cartResultVo.setShopReduce(moneyResult.getSubtractMone()); // 优惠金额
		cartResultVo.setTransfee(new BigDecimal("0.00")); // 运费的计算

		orderVo.setActualTotal(moneyResult.getFinalMoney().add(cartResultVo.getTransfee())); // 订单的实际金额 = 总金额-优惠金额（） + 运费
		orderVo.setTotal(moneyResult.getTotalMoney());
		orderVo.setTotalCount(moneyResult.getTotal());
		User user = (User)SecurityUtils.getSubject().getPrincipal();
		UserAddr userAddr = userAddrService.getUserDefaultAddr(user.getUserId());
		orderVo.setUserAddr(userAddr);

		// 缓存用户的订单信息，该缓存订单即将在 用户点击提交订单时取出它
		redisTemplate.opsForValue().set(ORDER_PRE+user.getUserId(), orderVo); //
		return ResponseEntity.ok(orderVo);
	}

	/**
	 * 这一步不是很重要，目的是给前端准备数据
	 * @param shopCartItems
	 * @return
	 */
	private List<CartResultVo> builderResult(List<ShopCartItem> shopCartItems) {
		List<CartResultVo> cartResultVos = new ArrayList<CartResultVo>(1);
		CartResultVo cartResultVo = new CartResultVo();
		List<ShopCartItemDiscount> shopCartItemDiscounts = new ArrayList<ShopCartItemDiscount>();
		ShopCartItemDiscount shopCartItemDiscount = new ShopCartItemDiscount();
		shopCartItemDiscount.setShopCartItems(shopCartItems); // 将购物车的数据放在里面
		shopCartItemDiscounts.add(shopCartItemDiscount );

		cartResultVo.setShopCartItemDiscounts(shopCartItemDiscounts );
		cartResultVos.add(cartResultVo) ;
		return cartResultVos;
	}

	@PostMapping("/p/order/submit")
	public ResponseEntity<Map<String, String>> submitOrder(){
		User user = (User)SecurityUtils.getSubject().getPrincipal();
		OrderVo orderVo = (OrderVo)redisTemplate.opsForValue().get(ORDER_PRE +user.getUserId());
		// 开始下单？
		// 1 使用redis 校验库存
		ShopCartItemDiscount shopCartItemDiscount = orderVo.getShopCartOrders().get(0).getShopCartItemDiscounts().get(0);
		List<ShopCartItem> shopCartItems = shopCartItemDiscount.getShopCartItems(); // 该集合里面包含所有的本次购买的商品的sku 和数量
		checkStock(shopCartItems); 	  // 该方法没有返回值，但是可能有异常，有异常代表有商品的数量不足
		// 2 将该订单对象放在mq 里面，放该订单进去之前，我们给他生成一个订单编号，类似你排队时，给你一个排队的号码，以后我拿该号去问别人，我的这个号搞定了没有
		// 生成一个编号有要求：1 编号唯一 2 生成速度快 3 有顺序 4 只能是数字类型
		// 1 时间 + 随机数  //2 使用时间+redis的自增（方案可以，但是速度一般） 3 // 雪花算法
		//workerId 机器的id datacenterId：应用的id
		String orderSn = snowFlake.nextIdStr(); // 订单的编号生成完成了
		orderVo.setOrderSn(orderSn); // 
		jmsTemplate.convertAndSend("order.pre.queue", orderVo); //只是放进去了，还没有下单成功了
		Map<String, String> result = MapUtil.builder("orderSn", orderSn).build();
		return ResponseEntity.ok(result); // 给用户一个排队的号码
	}

	private void checkStock(List<ShopCartItem> shopCartItems) {
		// 怎么使用redis 库存的校验 ？
		//1 库存量要放在redis 里面，这个solr 的导入类似，全量导入和新增，修改，购买的库存处理
		// 2 怎么做库存的校验？ stock:prod:1  stock:prod:2
		// 3 购买了6 个商品 前3 件库存够，第四件库存不足，我们现在只回滚了第4 件，前面3 件，没有回滚
		//  4 回滚时，不仅仅要考虑自己，还要考虑之前的值
		if(shopCartItems==null || shopCartItems.isEmpty()) {
			return ;
		}
		ValueOperations<String, String> opsForValue = redis.opsForValue();
		Map<String, Integer> successDecr = new HashMap<String,Integer>();
		for (ShopCartItem shopCartItem : shopCartItems) {
			Integer prodCount = shopCartItem.getProdCount();
			Long prodId = shopCartItem.getProdId();
			Long rematin = opsForValue.decrement(PROD_STOCK+prodId, prodCount);
			if(rematin<0) { // 减少完后还有值 10 - 20  = -10 // 这个没有问题
				opsForValue.increment(PROD_STOCK+prodId, prodCount);
				successDecr.forEach((k,v)->{
					opsForValue.increment(k, v);
				});
				throw new RuntimeException("商品"+prodId+"库存不足") ;
			}
			successDecr.put(PROD_STOCK+prodId, prodCount) ;
		}

	}

	// 1 为什么查询可以远程调用？ 查询速度快 -线程快速回收
	// 2 下单不能远程调用？下单速度慢-> 线程不能及时回收，可能导致503
	// http://127.0.0.1:8086/p/order/pay?payType=1&orderNumbers=1214395037648031700
	@GetMapping("/p/order/pay")
	public ResponseEntity<String> queryOrder(@RequestParam(required = true)String orderSn){
		OrderSettlement orderSettlement = orderSettlementService.queryOrderByOrderSn(orderSn);
		if(orderSettlement!=null) { // 代表该订单已经下次成功了
			BigDecimal payAmount = orderSettlement.getPayAmount();
			PayModel model = new PayModel();
			// 我们此时可以生成一个支付的连接
			model.setSubject("【ego商城订单】"+orderSn);
			model.setOutTradeNo(orderSn);
			model.setPayType(1);
			model.setTotalAmount(payAmount.toString());
			model.setBody("【ego商城订单】："+orderSn+"，总金额为："+payAmount.toString());
			String aliPay = egoPayService.aliPay(model );
			return ResponseEntity.ok(aliPay) ;
		}
		return ResponseEntity.ok().build();
	}

	/**
	 * 前端将2 s 一次查询，来判断订单是否支付成功了
	 * @param orderSn
	 * @return
	 */
	@GetMapping("/p/order/isPay")
	public ResponseEntity<Boolean> isPayed(@RequestParam(required = true) String orderSn){
		OrderSettlement orderSettlement = orderSettlementService.queryOrderByOrderSn(orderSn);
		Integer payStatus = orderSettlement.getPayStatus();
		if(payStatus!=null &&payStatus.equals(1)) {
			return ResponseEntity.ok(Boolean.TRUE) ;
		}
		return ResponseEntity.ok(Boolean.FALSE) ;
	}
	/**
	 * 怎么确定是支付宝访问的？
	 * @param orderSn
	 * 需要一个验证签名
	 *  我们之前在配置文件有个支付宝的公钥alipay_public_key
	 *  支付宝使用私钥加密得到一个签名，若你用公钥能验证成功，代表你和支付宝是正确的
	 *  私钥：能制造签名
	 *  公钥：能验证签名
	 *  OAUTH2.0 里面有个非对称加密
	 * @return
	 */
	@PostMapping("/p/order/notify/{orderSn}")
	public ResponseEntity<String> orderNotify(
			@PathVariable("orderSn")String orderSn,
			HttpServletRequest request
			){
		System.out.println("支付宝它来通知我们了");

		//获取支付宝POST过来反馈信息
		Map<String,String> params = new HashMap<String,String>();
		Map<String,String[]> requestParams = request.getParameterMap(); // 得到支付宝的所有参数
		for (Iterator<String> iter = requestParams.keySet().iterator(); iter.hasNext();) {
			String name = (String) iter.next();
			String[] values = (String[]) requestParams.get(name);
			String valueStr = "";
			for (int i = 0; i < values.length; i++) {
				valueStr = (i == values.length - 1) ? valueStr + values[i]
						: valueStr + values[i] + ",";
			}
			params.put(name, valueStr);
		}
		boolean isOk = egoPayService.checkRASSign(params);
		if(isOk) {
			System.out.println(orderSn);
			orderSettlementService.updateOrderStatus(orderSn) ;
			return ResponseEntity.ok("zfb ok") ;
		}else {
			return ResponseEntity.badRequest().body("黑客你好，你的信息我已经记录，请主动自首");
		}

	}
}
